7W - 실리움 성능 - 쿠버네티스 기본

개요

이번 주차는 운영에서 중요한 업무 중 하나인 성능 최적화에 대한 부분을 알아본다.
실리움의 성능 튜닝을 알아보기 이전에, 쿠버네티스에서 신경 써야 할 성능 포인트를 먼저 짚고 들어간다.
아울러 성능 측정에 도움을 주는 툴도 알아보도록 한다.

kube-burner


kube-burner는 쿠버네티스 클러스터의 성능을 테스트하는 도구로 불내는데(?) 유용하다.[1]
쿠버네티스 Go 클라이언트 라이브러리인 client-go를 이용에 테스트에 용이하도록 기능을 만든 어플리케이션이다.

기본적으로 api 서버에 대한 여러 요청을 날릴 수 있고 프로메테우스 기반 모니터링, 측정, 알람 기능도 갖추고 있다.
테스트에 사용할 양식 파일을 먼저 만들고, 이후에 kube-burner에서 지원하는 커스텀 리소스 양식으로 이를 참조한 뒤에 사용한다.
그래서 원하는 테스트 상황을 마음대로 만들 수 있다는 장점이 있다.

사용법

사용할 커맨드가 많지 않다.

check-alerts Evaluate alerts for the given time range 주어진 시간 범위 내의 알람 평가
completion   Generates completion scripts for bash shell
destroy      Destroy old namespaces labeled with the given UUID. 사용된 네임스페이스 삭제
health-check Check for Health Status of the cluster 클러스터 헬스체크
help         Help about any command
import       Import metrics tarball tarball 압축 파일에서 메트릭 불러오기
index        Index kube-burner metrics 메트릭 인덱싱(start, end 시간으로 범위 지정)
init         Launch benchmark 벤치마크 실행
measure      Take measurements for a given set of resources without running workload 워크로드 실행 없이 리소스만 테스트
version      Print the version number of kube-burner 

보다시피 init이 벤치마킹을 수행하는 명령어이다.
여기에 -c 옵션으로 설정 파일을 넣어서 실행한다.

벤치마킹의 반환 코드는 다음의 뜻을 가진다.

설정 파일 작성법

go-template 양식의 파일을 지원한다.

metricsEndpoints:
{{ if .OS_INDEXING }}
  - prometheusURL: http://localhost:9090
    indexer:
      type: opensearch
      esServers: ["{{ .ES_SERVER }}"]
      defaultIndex: {{ .ES_INDEX }}
{{ end }}
{{ if .LOCAL_INDEXING }}
  - prometheusURL: http://localhost:9090
    indexer:
      type: local
      metricsDirectory: {{ .METRICS_FOLDER }}
{{ end }}

이때 파일은 크게 몇 가지 대범주의 필드로 작성한다.

위 4가지 유형의 잡타입 말고, churn이란 동작을 하는 것도 가능하다.

jobs:
- name: cluster-density
  jobIterations: 100
  namespacedIterations: true
  namespace: churning
  churn: true
  churnPercent: 20
  churnDuration: 2h
  churnDelay: 0s
  objects:
  - objectTemplate: deployment.yml
    replicas: 10
  - objectTemplate: service.yml
    replicas: 10

churn은 이전 오브젝트를 지우고 재생성하는 동작이다.
잡 순회를 하며 이전 오브젝트를 일부 지우고 그대로 새로 리소스를 만드는 식으로 동작한다.
휘젓는 느낌이라 churn으로 부르는 모양이다.

오브젝트 파일

기본으로 오브젝트에 주입되는 변수는 다음과 같다.

설명은 생략하고 사용예시만 본다.

apiVersion: v1
kind: Service
metadata:
  name: sleep-app-{{.Iteration}}-{{.Replica}}
  labels:
    name: my-app-{{.Iteration}}-{{.Replica}}
spec:
  selector:
    app: sleep-app-{{.Iteration}}-{{.Replica}}
  ports:
  - name: serviceport
    protocol: TCP
    port: "{{.port}}"
    targetPort: "{{.targetPort}}"
  type: ClusterIP

여기에 여러 함수를 사용할 수 있는데, 기본 고랭 템플릿과 sprig 라이브러리[2]의 함수들이 지원된다.

측정 지표

위의 measurements에서 설정할 수 있는 지표는 여러 가지가 있다.
기본적으로 지표를 넣으면 측정 수치가 나온다(아래는 podLatency 예시).

{
  "timestamp": "2020-11-15T20:28:59.598727718Z",
  "schedulingLatency": 4,
  "initializedLatency": 20,
  "containersReadyLatency": 2997,
  "podReadyLatency": 2997,
  "metricName": "podLatencyMeasurement",
  "uuid": "c40b4346-7af7-4c63-9ab4-aae7ccdd0616",
  "namespace": "kubelet-density",
  "podName": "kubelet-density-13",
  "nodeName": "worker-001",
  "jobName": "create-pods",
  "jobIteration": "2",
  "replica": "3",
}

이에 대한 메트릭 지표는 다음과 같은 방식으로 합쳐진다.

{
  "quantileName": "Ready",
  "uuid": "23c0b5fd-c17e-4326-a389-b3aebc774c82",
  "P99": 3774,
  "P95": 3510,
  "P50": 2897,
  "max": 3774,
  "avg": 2876.3,
  "timestamp": "2020-11-15T22:26:51.553221077+01:00",
  "metricName": "podLatencyQuantilesMeasurement",
},
{
  "quantileName": "PodScheduled",
  "uuid": "23c0b5fd-c17e-4326-a389-b3aebc774c82",
  "P99": 64,
  "P95": 8,
  "P50": 5,
  "max": 64,
  "avg": 5.38,
  "timestamp": "2020-11-15T22:26:51.553225151+01:00",
  "metricName": "podLatencyQuantilesMeasurement",
}

자세한 측정 지표들에 대한 정보는 문서를 확인한다.[3]

실습 환경 구성

성능 테스트를 하는 만큼, 자원이 불필요하게 많이 쓰이는 가상환경에서 실습하기에는 조금 부담스럽다.
그래서 이번에는 kind를 사용한다.

kind create cluster --name myk8s --image kindest/node:v1.33.2 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
  - containerPort: 30002
    hostPort: 30002
  - containerPort: 30003
    hostPort: 30003
  kubeadmConfigPatches: # Prometheus Target connection refused bind-address 설정
  - |
    kind: ClusterConfiguration
    controllerManager:
      extraArgs:
        bind-address: 0.0.0.0
    etcd:
      local:
        extraArgs:
          listen-metrics-urls: http://0.0.0.0:2381
    scheduler:
      extraArgs:
        bind-address: 0.0.0.0
  - |
    kind: KubeProxyConfiguration
    metricsBindAddress: 0.0.0.0
EOF

각 컴포넌트들에 대해 모든 주소로 리스닝할 수 있도록 추가 인자를 전달한다.
이 설정을 넣어줘야 프로메테우스에서 메트릭을 편하게 수집할 수 있다.

이 다음에는 모니터링에 사용할 kube-ops-view, 프로메테우스를 세팅한다.

# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30003 --set env.TZ="Asia/Seoul" --namespace kube-system
open "http://localhost:30003/#scale=1.5"
open "http://localhost:30003/#scale=2"

# metrics-server
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm upgrade --install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system

# 확인
kubectl top node
kubectl top pod -A --sort-by='cpu'
kubectl top pod -A --sort-by='memory'

# 파라미터 파일 생성
cat <<EOT > monitor-values.yaml
prometheus:
  prometheusSpec:
    scrapeInterval: "15s"
    evaluationInterval: "15s"
  service:
    type: NodePort
    nodePort: 30001

grafana:
  defaultDashboardsTimezone: Asia/Seoul
  adminPassword: prom-operator
  service:
    type: NodePort
    nodePort: 30002

alertmanager:
  enabled: false
defaultRules:
  create: false
prometheus-windows-exporter:
  prometheus:
    monitor:
      enabled: false
EOT
cat monitor-values.yaml

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# 배포
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 75.15.1 \
-f monitor-values.yaml --create-namespace --namespace monitoring

# 웹 접속 실행
open http://127.0.0.1:30001 # macOS prometheus 웹 접속
open http://127.0.0.1:30002 # macOS grafana 웹 접속 ( admin , prom-operator )

그라파나 대시보드 두 개를 다운 받아 사용할 것이다.

curl -o 15661_rev2.json https://grafana.com/api/dashboards/15661/revisions/2/download
sed -i "s/\${DS__VICTORIAMETRICS-PROD-ALL}/prometheus/g" 15661_rev2.json
k create cm 15661 -n monitoring --from-file 15661_rev2.json
k -n monitoring label cm 15661 grafana_dashboard=1

curl -o 12006_rev1.json https://grafana.com/api/dashboards/12006/revisions/1/download
k create cm 12006 -n monitoring --from-file 12006_rev1.json
k -n monitoring label cm 12006 grafana_dashboard=1

컨피그맵으로 설정 파일을 넣고 grafana_dashboard=1이라고 라벨만 달아주면 알아서 그라파나가 이를 인식하고 대시보드로 올려준다.
이번에 파드, 노드를 많이 넣어 테스트할 거라 유용한 대시보드들이다.
image.png
두번째 대시 보드는 조금 오래 됐지만 latency를 확인하기에 유용하다.
image.png

kube-burner 테스트

본격적으로 부하 테스트를 한번 시도해본다.

#
git clone https://github.com/kube-burner/kube-burner.git
cd kube-burner

# 바이너리 설치(추천) : mac M1
curl -LO https://github.com/kube-burner/kube-burner/releases/download/v1.17.3/kube-burner-V1.17.3-darwin-arm64.tar.gz # mac M
tar -xvf kube-burner-V1.17.3-darwin-arm64.tar.gz

curl -LO https://github.com/kube-burner/kube-burner/releases/download/v1.17.3/kube-burner-V1.17.3-linux-x86_64.tar.gz # Windows
tar -xvf kube-burner-V1.17.3-linux-x86_64.tar.gz

sudo cp kube-burner /usr/local/bin

image.png

먼저 사용법을 익히는 차원에서 디플로이먼트 1개를 생성하고 삭제하는 테스트를 해본다.
테스트용 설정 파일을 먼저 만든다.

global:
  measurements:
    - name: none

jobs:
  - name: create-deployments
    jobType: create
    jobIterations: 1  # How many times to execute the job , 해당 job을 5번 반복 실행
    qps: 1            # Limit object creation queries per second , 	초당 최대 요청 수 (평균 속도 제한) - qps: 10이면 초당 10개 요청
    burst: 1          # Maximum burst for throttle , 순간적으로 처리 가능한 요청 최대치 (버퍼) - burst: 20이면 한순간에 최대 20개까지 처리 가능
    namespace: kube-burner-test
    namespaceLabels: {kube-burner-job: delete-me}
    waitWhenFinished: true # false
    verifyObjects: false
    preLoadImages: true # false
    preLoadPeriod: 30s # default 1m
    objects:
      - objectTemplate: s1-deployment.yaml # 이걸 대상으로 삼겠다는 뜻
        replicas: 1

오브젝트에는 테스트하고자 하는 양식 파일을 삽입하면 된다.
대충 설명하자면 생성하는 잡 수행 횟수는 한번, 1초에 쿼리는 1번에 최대로도 1번만 날릴 수 있다.
이때 테스트용 네임스페이스를 만들거고, 테스트에 사용될 이미지를 미리 받은 상태로 수행할 것이다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-{{ .Iteration}}-{{.Replica}}
  labels:
    app: test-{{ .Iteration }}-{{.Replica}}
    kube-burner-job: delete-me
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-{{ .Iteration}}-{{.Replica}}
  template:
    metadata:
      labels:
        app: test-{{ .Iteration}}-{{.Replica}}
    spec:
      containers:
        - name: nginx
          image: nginx:alpine
          ports:
            - containerPort: 80

Go 템플릿이 적용되기 때문에 몇 가지 활용한 것이 보인다.

모니터링은 터미널과 kube-ops-view를 같이 쓴다.
네임스페이스도 늘어나고 노드도 늘어나니까.

# 모니터링 : 터미널, kube-ops-view
watch -d kubectl get ns,pod -A

kube-burner init -c s1-config.yaml --log-level debug

이미지를 미리 받아두는 데몬셋이 테스트 전 배치된 것을 확인할 수 있다.
image.png
전체 작업이 끝나면 해당 테스트 UUID를 이름으로 하는 로그 파일이 만들어진다.
별 건 아니고 출력됐던 로그가 그대로 들어있다.
image.png

간단하게 생성 잡에 대해 테스트를 했으니 삭제도 진행한다.

jobs:
  - name: delete-deployments-namespace
    qps: 500
    burst: 500
    namespace: kube-burner-test
    jobType: delete
    waitWhenFinished: true
    objects:
    - kind: Deployment
      labelSelector: {kube-burner-job: delete-me}
      apiVersion: apps/v1
    - kind: Namespace
      labelSelector: {kube-burner-job: delete-me}

라벨 기반으로 오브젝트를 선택하여 지우는 것을 보여주기 위해 이전 양식에서 라벨을 부착했다.

익숙해지는 차원에서 여러 값들을 수정하면서 테스트해본다.
image.png
qps는 초당 날리는 쿼리의 개수를 조절하는데, 이 기준은 잡 당 적용된다.
그래서 burst는 낮으면서 qps가 높아도 한번에 모든 잡이 실행되는 게 아니라 잡을 넘어가는데 시간이 발생한다.
반면 burst는 잡보다 더 큰 관점에서의 세팅값이다.
위처럼 오브젝트에 레플리카를 복수로 두고 잡 횟수를 늘려 테스트하면 레플리카 * 잡 회차가 burst에 미칠 때까지 순식간에 잡이 진행되고 이후 qps의 영향을 받는 것을 확인할 수 있다.

실행과 삭제를 같이 진행하면서 퍼센트 기반으로 잡 실행을 조절하고 싶다면 churn 기능을 쓰면 된다.
단순히 실행, 삭제를 한번에 하고 싶은 거라면 리스트로 생성 삭제 잡을 같이 넣어줘도 된다.

jobs:
  - name: create-deployments
    churn: true
    churnDelay: 10s
    jobIterations: 10
    qps: 10           
    burst: 100     
    namespace: kube-burner-test
    namespaceLabels: {kube-burner-job: delete-me}
    waitWhenFinished: true # false
    verifyObjects: false
    preLoadImages: false
    preLoadPeriod: 30s # default 1m
    objects:
      - objectTemplate: s1-deployment.yaml

image.png

파드 과다 테스트

이제부터 부하가 갈 만한 상황을 만들어본다.

    jobIterations: 100
    qps: 300         
    burst: 300      

더 이상 배치되지 않고 내동댕이쳐진 파드들이 눈에 보인다.
image.png
describe를 하면 파드가 많아 노드에 스케줄링을 실패했다는 것을 확인할 수 있다.
image.png
노드의 정보를 보면 할당 가능한 파드의 개수가 최대 110개로 걸려있는 상황이다.
image.png
이걸 해결하려면 kubelet에 걸리는 인자를 수정해줘야 한다.
해결은 이렇게 할 수 있다.

docker exec -it myk8s-control-plane bash -c "echo maxPods: 400 >> /var/lib/kubelet/config.yaml"
docker exec -it myk8s-control-plane systemctl restart kubelet
docker exec -it myk8s-control-plane  systemctl status kubelet

kubelet 프로세스 자체를 다시 띄워야 하는 일이라 api 서버를 거치는 것은 불가능하다.
image.png
사진은 400으로 최대 개수를 늘리기 이전 테스트를 위해 150으로 설정했다.

그럼 아예 더 못하게 파드 개수를 왕창 늘려보자.
kubelet 제한 뿐 아니라, IP 대역도 파드의 개수를 제한하는 하나의 요소가 된다.

    jobIterations: 300
    qps: 300
    burst: 300

이번에는 스케줄은 전부 됐지만, 막상 노란색으로 점등되는 파드들이 산더미다.
image.png
이 파드들은 IP 주소를 할당 받지 못해 Container Creating 단계에서 에러가 발생하고 있다.
image.png
이 문제는 쉽게 해결하기 어렵다.
IP 주소를 관리하는 것은 CNI의 설정을 바꿔야 하는 사항이고, 현재 사용 중인 kindnet의 경우 동적으로 IP를 추가하는 기능은 지원하지 않는다.

api 집약적 작업 테스트

kube-burner에 있는 예시 테스트 중 하나를 수행해본다.
원래는 다 해볼 생각이었는데, 결국 테스트할 부하 발생 지점이 유달리 다르게 보이지는 않아서 다양한 기능을 찾아보는데 조금 더 주력한다.
일단 첫번째는 api에 단순하게 작업을 많이 요하는 일을 시키는 것이다.
image.png
이 잡 위에는 정말 별 거 없는 리소스들을 700개 가까이 만든다.
그리고는 patch 작업을 때려버리는 것이다.

아쉽게도 실험이 잘 진행되진 않았다. 도중에 또 IP 부족 문제가 터져 의미 없게 시간이 낭비됐다.
image.png
다만 위처럼 자원 사용량이 늘어나는 것까지는 확인이 됐다.
이후 실험에서는 qps, burst을 한껏 올려 진행했다.
image.png
이번엔 생각했던 상황이 연출되기는 했다.
사실 OOM 터지는 꼴 보고 싶었는데 실패
많은 요청이 들어왔고, 순간 요청이 500개 가까이 들어온 순간도 있었다.

모범 모니터링 쿼리 예제를 이용해서 확인해봤다.[5]
image.png
테스트가 되던 당시 요청 성공률이 절반으로 뚝 떨어지는 순간이 있었다.
들어오는 요청에 rate limiting이 이미 걸렸던 것으로 생각된다.
APF는 기본으로 설정돼있으니 이미 적용된 것일 수 있겠다.

문제 분석

빠르게 테스트를 한답시고 계속 지나쳤던 사항인데, 동작할 때 지속적으로 완전히 멈추는 구간이 발생했다.
image.png
지나치게 높게 버스트를 걸어서 아무래도 요청 중 드랍이 되는 것들이 있었던 게 아닐까 싶다.
드랍이 되고 요청이 수행되지 않아 다음으로 넘어가지 않고 파이프라인이 그대로 행된 것이 아닐까 추측된다.
메트릭을 봐도 지속적인 api 요청이 들어가기는 하는데, list 요청만 들어가고 있다.
image.png

그렇다면 api 요청 중에 429나, 관련한 에러 지표가 나왔을 거라 생각이 들어 찾아봤는데, 관련 메트릭 정보를 찾기는 어려웠다.
image.png
헬스체크가 실패하는 지표가 계속 관측되는데, 이것이 관련이 있을지는 모르겠다.
컨테이너 메트릭 정보도 찾아본다.
image.png
프로메테우스 컨테이너가 써먹는 자원이 많다는 것은 확인했다.

apf와 관련하여 문제가 생긴 것은 아닐 거라는 생각이 추가적으로 들게 됐는데, 만약 요청이 거부된다면 베타로 제공되는 rejected 관련 메트릭이 관측됐어야 할 것으로 보인다.[6]
image.png
그러나 막상 그러한 메트릭은 찾아볼 수 없었다.

그렇다면 마지막으로 의심 가는 것은 ip를 부여 받지 못해 펜딩된 파드를 처리하지 못해 지연이 발생하는 케이스이다.
image.png
펜딩 상태의 파드가 아직 수두룩하게 박혀 있고, 공교롭게도 현재 펜딩이 파드의 번호부터 파이프라인이 중단됐다.
image.png
이게 문제라면, 빠르게 부하를 내려했던 것 자체가 문제는 아니라는 이야기이기도 하다.

메트릭 확인

kube-burner의 기능 중 하는 메트릭과 연결해서 분석하는 것이다.
이를 위해 테스트가 될 당시의 모니터링 도구에 접근해 필요한 메트릭을 가져오고 이것을 어딘가에 넣을 수 있게 인덱싱한다.
인덱서로는 opensearch, elastic도 가능한데 로컬에 저장해야 당장 보기가 편하다.

아래와 같이 어디에서 메트릭을 긁어올지, 그럼 어떤 데이터를 긁을지, 이후 이걸 어디에 보낼 건지 확인하면 된다.

metricsEndpoints:
  - endpoint: http://localhost:30001
    metrics:
      - etcdapi.yml
    indexer:
      metricsDirectory: /tmp/kube-burner
      type: local

메트릭 파일은 이렇게 쿼리에 사용할 쿼리를 다룬 자료이다.
image.png
내 경우는 로컬로 설정하여 작업이 끝나면 이렇게 여러 json 파일이 생기는 것을 확인할 수 있다.
image.png
각가 json 형태로 메트릭 정보가 들어있는데 이걸 어떻게 시각화할진 조금 알아봐야겠다.
image.png

APF

API Priority and Fairness는 kube-apiserver로 들어가는 요청에 섬세한 제한을 거는 기능이다.
크게 보면 rate limiting이지만, APF는 최대 요청 제한에 대한 설정을 더 세밀하게 할 수 있도록 지원하며
또한 공정하게 처리될 트래픽을 분배하는 큐 알고리즘을 도입하여 특정 요청이 계속 처리되지 못하는 상황을 방지한다.

APF는 watch 요청에 대해서만 적용된다는 점 참고하자.
exec이나 log와 같은 커넥션이 긴 다른 요청에 대해선 간섭하지 않는다.

APF는 --enable-priority-and-fairness 인자로 전달하여 설정하며 기본 활성화 상태이다.
또한 관련 리소스가 존재하며 현재 v1 상태이다.

개념

동시성, 중요도(Priority Level)

단위 시간 동안 서버가 동시에 처리할 수 있는 양(concurrency capacity)은 정해져 있다.
이 수용량은 api 서버 인자 --max-requests-inflight--max-mutating-requests-inflight를 통해 설정할 수 있다.
아무튼 문서에서는 동시성의 기본 단위를 seat이라 하는데, 이는 열차나 비행기에 전체 탑승 인원이 "좌석"을 기반으로 결정된다는 점에서 착안된 용어다.
예를 들면 100개의 자리가 있다면 한번에 서버는 100개의 요청을 처리할 수 있을 것이다.
물론 어떤 요청은 처리에 시간이 오래 걸리거나 많은 연산을 수행해야 하여 더 많은 자리를 차지할 수도 있다.
그래서 자리는 그저 동시성 수용량을 부분으로 쪼개 이해하기 위한 개념 정도로 생각하면 될 듯하다.

APF는 이 동시성 수용량을 쪼개 그룹 별로 할당하는 역할을 수행한다.
API 서버로 들어오는 요청의 속성 기반으로 분류한 뒤, 분류된 그룹에 따라 정책을 적용하는 방식으로 분류된 그룹을 Priority Level(중요도라 번역하겠다)이라 부른다.
중요도는 요청을 동시에 처리할 수 있는 공간을 나눠가지는 단위이기에, 다른 중요도에 속한 요청들은 서로의 자원을 뺏는 일이 발생하지 않는다.
그래서 파드에서 오는 요청이 많더라도 파드 스케줄링을 위한 kubelet의 요청에는 영향이 가지 않도록 세팅하는 것이 가능하다.

중요도 별 동시성 제한은 주기적으로 조정된다.
가령 활용도가 낮은 중요도 그룹의 동시성 수용량을 줄이고 부하가 큰 중요도 그룹의 수용량을 넓혀준다!
조정이 일어나는 범위에 대해서는 제한을 두거나 설정하는게 가능한데, 아래 리소스에서 자세히 보도록 한다.

중요도가 다른 그룹 간에는 서로 경합이 일어나지 않지만, 중요도 내의 여러 요청은 자원을 나눠써야 하는 입장이다.
또한 한 중요도에 많은 요청이 몰리면 당연히 버려지는 요청도 생기게 될 것이다.
APF는 이에 대해 큐를 사용한다.
특히 공정한 큐 알고리즘을 사용해 중요도 내 여러 요청들이 공평하게 수행될 수 있도록 돕는다.
구체적으로는 셔플 샤딩(큐에 해싱 나머지 연산을 이용해 배치)을 사용하는데, 알고리즘은 중요도 별로 유형을 달리 할 수 있다.
이러한 튜닝을 통해 큐에서 메모리를 더 사용하도록 공간을 넓힐 수도 있고, 아예 큐를 두지 않는 것도 가능하다.

각 요청은 요청자, 대상 네임스페이스 등을 구분자로 매칭되는 flow schema로 식별되는 플로우을 하나씩 지정받는다.
같은 중요 레벨의 플로우들은 거의 같은 가중치를 부여한다.
한 플로우에서 요청을 분류하면 그 다음에 큐에 배치한다.

관련 리소스

위의 개념을 바탕으로 APF에서는 두 가지 리소스를 제공한다.

PriorityLevelConfiguration

이건 중요도 그룹을 지정하는 리소스로, 그룹 별 동시성 수용량, 큐 튜닝 등의 설정을 할 수 있다.
중요도 수치를 설정할 수도 있는데, 이건 전체 동시성 양에서 비율적으로 산정된다.
그래서 이를 명목 상(nominal)의 숫자라고도 표현한다.

다음은 큐 관련 설정이다.

FlowSchema

요청의 속성을 조건으로 중요도 그룹에 매핑하는 설정 리소스이다.

apiVersion: flowcontrol.apiserver.k8s.io/v1
kind: FlowSchema
metadata:
  name: health-for-strangers
spec:
  matchingPrecedence: 1000
  priorityLevelConfiguration:
    name: exempt
  rules:
    - nonResourceRules:
      - nonResourceURLs:
          - "/healthz"
          - "/livez"
          - "/readyz"
        verbs:
          - "*"
      subjects:
        - kind: Group
          group:
            name: "system:unauthenticated"

ByUser - 다른 유저의 수용량을 뺏지 않음
ByNamespace - 네임스페이스별 수용량을 뺏지 않음
설정 안하면 그냥 설정 안된다.
그럼 모든 요청이 하나의 단일 플로우로 인식될 것이다.

기본 APF

api 서버에는 mandatory, suggested 두 유형으로 먼저 APF 기능이 들어가 있다.

mandatory

여기에는 2개의 중요도가 들어있다.

suggested

APF 확인

많이 사용해본 적도 없고, 구체적으로 어떻게 설정하는지 명확하게는 머리에 잘 안 들어와서 이번 실습에서는 확인하는 시간 정도만 가진다.

플로우 스키마, 중요도 설정 모두 기본으로 설정된 리소스가 확인된다.
image.png
스키마는 매칭 우선순위에 따라 정렬돼 있는 것을 확인할 수 있다.

보다시피 system 그룹이면 exempt, 즉 예외 중요도 그룹으로 매핑된다.
image.png
그러면 해당 요청들은 apf에 당하지 않고 그대로 요청이 수행된다.

apf는 현재 관련한 메트릭도 프로메테우스 양식으로 공개하고 있다.
image.png

결론

실리움 스터디에 실리움보다 쿠버네티스 자체 최적화에 내용을 더 디테일하게 다뤘다..
그러나 결국 운영 상에서 어떤 부분에서 병목이 발생하는지는 비슷하기 때문에, 실리움을 본격적으로 파기 전에 클러스터 자체에 대한 성능을 최적화하는 기법을 알아두는 것은 충분한 도움이 될 것이다.

이전 글, 다음 글

다른 글 보기

이름 index noteType created
1W - 실리움 기본 소개 1 published 2025-07-19
1W - 클러스터 세팅 및 cni 마이그레이션 2 published 2025-07-19
1W - 기본 실리움 탐색 및 통신 확인 3 published 2025-07-19
2W - 허블 기반 모니터링 4 published 2025-07-26
2W - 프로메테우스와 그라파나를 활용한 모니터링 5 published 2025-07-26
3W - 실리움 기본 - IPAM 6 published 2025-08-02
3W - 실리움 기본 - Routing, Masq, IP Frag 7 published 2025-08-02
4W - 실리움 라우팅 모드 실습 - native, vxlan, geneve 8 published 2025-08-09
4W - 실리움 로드밸런서 기능 - 서비스 IP, L2 9 published 2025-08-09
5W - BGP 실습 10 published 2025-08-16
5W - 클러스터 메시 11 published 2025-08-16
6W - 실리움 서비스 메시 - 인그레스 12 published 2025-08-23
7W - 실리움 성능 - 쿠버네티스 기본 13 published 2025-08-31
8W - 실리움 보안 14 published 2025-09-07

관련 문서

지식 문서, EXPLAIN

이름3is-folder생성 일자
E-이스티오 컨트롤 플레인 성능 최적화false2025-05-18 02:29
클러스터 성능 최적화false2025-08-30 17:02
API Priority and Fairnessfalse2025-08-30 17:08

기타 문서

Z0-연관 knowledge, Z1-트러블슈팅 Z2-디자인,설계, Z3-임시, Z5-프로젝트,아카이브, Z8,9-미분류,미완
이름1코드타입생성 일자
6W - 이스티오 컨트롤 플레인 성능 최적화Z8published2025-05-18 02:29

참고


  1. https://github.com/kube-burner/kube-burner ↩︎

  2. https://masterminds.github.io/sprig/ ↩︎

  3. https://kube-burner.github.io/kube-burner/latest/measurements/#metrics_1 ↩︎

  4. https://github.com/kube-burner/kube-burner/blob/main/examples/workloads/network-policy/network-policy.yml ↩︎

  5. https://www.redhat.com/en/blog/kubernetes-api-performance-metrics-examples-and-best-practices ↩︎

  6. https://kubernetes.io/docs/concepts/cluster-administration/flow-control/#maturity-level-beta ↩︎